﻿using Devil;
using LitJson;
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using System.Threading;
using UnityEngine;

namespace GameToolkit
{
    public interface IAssetVariantGroup
    {
        string Title { get; }
        string ActiveVariant { get; }
        int Length { get; }
        string this[int index] { get; }
        bool IsActiveVariant(string variant);
        bool IsVariantAsset(string path);
        string GetAssetAddress(string path);
    }

    [System.Flags]
    public enum EVariantGroupQuery
    {
        Variant = 1,
        Group = 2,
        AssetPath = 4,
    }

    public class AssetBundleVariant : Disposable, ICommandHandler
    {
        public IAssetVariantGroup GetABInfo(string ab, out string abName, out string variant)
        {
            if (string.IsNullOrEmpty(ab))
            {
                abName = ab;
                variant = null;
                return null;
            }
            var n = ab.LastIndexOf('.');
            if (n > 0)
            {
                variant = ab.Substring(n + 1);
                var grp = GetVariantGroup(EVariantGroupQuery.Variant, variant);
                if (grp != null)
                {
                    abName = ab.Substring(0, n);
                }
                else
                {
                    abName = ab;
                    variant = null;
                }
                return grp;
            }
            else
            {
                abName = ab;
                variant = null;
                return null;
            }
        }

        // 默认状态
        readonly int STAT_DEFAULT = 0;
        // 初始化中
        readonly int STAT_INITIALIZING = 1;
        // 初始化完成
        readonly int STAT_INITIALIZED = 2;
        // 激活变体
        readonly int STAT_ACTIVITING = 3;
        // 初始化错误
        readonly int STAT_ERROR = 4;

        // before change keywords
        public event System.Action<SliceList<IAssetVariantGroup>> OnWillChangeVariants;
        // before load asset by keywords
        public event System.Action<SliceList<IAssetVariantGroup>> OnReloadVariants;
        // after loaded asset by keywords
        public event System.Action<SliceList<IAssetVariantGroup>> OnLoadedVariants;

        class Group : IAssetVariantGroup
        {
            string mTitle;
            public string Title => mTitle;

            string[] mVariants;
            public int Length => mVariants.Length;
            public string this[int index] => mVariants[index];

            int mActiveIndex;
            public string ActiveVariant => mVariants[mActiveIndex];

            Regex mReplacePathPattern;
            public int GetVariantIndex(string variant)
            {
                for (int i = mVariants.Length - 1; i >= 0; i--)
                {
                    if (variant.EqualsIgnoreCase(mVariants[i]))
                    {
                        return i;
                    }
                }
                return -1;
            }

            internal bool SetVariantActive(string keyword)
            {
                var index = GetVariantIndex(keyword);
                if (index == mActiveIndex || index == -1)
                    return false;
                mActiveIndex = index;
                return true;
            }

            internal Group(string tilte, JsonData arr)
            {
                mTitle = tilte;
                mVariants = new string[arr.Count];
                if (mVariants.Length == 0)
                    throw new System.ArgumentException("empty asset's keyword");
                for (int i = 0; i < mVariants.Length; i++)
                {
                    mVariants[i] = (string)arr[i];
                    if (string.IsNullOrEmpty(mVariants[i]))
                        throw new System.ArgumentException("empty asset's keyword");
                }
            }

            internal Regex GetVariantPathPattern()
            {
                if (mReplacePathPattern == null)
                {
                    var buf = ParallelUtils.GetBuilder();
                    buf.Append("/(");
                    for (int i = 0; i < mVariants.Length; i++)
                    {
                        if (i > 0)
                            buf.Append('|');
                        buf.Append(mVariants[i]);
                    }
                    buf.Append(")/");
                    mReplacePathPattern = new Regex(ParallelUtils.ReturnRelease(buf), RegexOptions.IgnoreCase);
                }
                return mReplacePathPattern;
            }

            public bool IsActiveVariant(string variant)
            {
                return mVariants[mActiveIndex].EqualsIgnoreCase(variant);
            }

            public string GetAssetAddress(string path)
            {
                return GetVariantPathPattern().Replace(path, "/");
            }

            public bool IsVariantAsset(string path)
            {
                return GetVariantPathPattern().IsMatch(path);
            }
        }

        int mState;
        string mCfgPath;
        Group[] mVariantGroups;
        List<IAssetVariantGroup> mDirtyGroups;
        ParallelDispatcher.Handler mChangCmd;
        ParallelDispatcher.CombinedHandler mDuringChangeCmd;
        DoActiviteCmd mActivotor;
        public bool IsInitialized => mState == STAT_INITIALIZED;
        public int Length => mVariantGroups == null ? 0 : mVariantGroups.Length;
        public IAssetVariantGroup this[int index] => mVariantGroups[index];

        public AssetBundleVariant(string cfgPath)
        {
            mCfgPath = cfgPath;
            mDirtyGroups = new List<IAssetVariantGroup>();
            mActivotor = new DoActiviteCmd(this);
        }

        public int GroupCount => mVariantGroups.Length;

        public IAssetVariantGroup GetGroup(int index)
        {
            return mVariantGroups[index];
        }

        protected override void OnDisposing()
        {
            Interlocked.Exchange(ref mState, STAT_ERROR);
            OnWillChangeVariants = null;
            OnReloadVariants = null;
            OnLoadedVariants = null;
        }

        public ParallelDispatcher.Handler GetHandler()
        {
            return mChangCmd;
        }

        public bool Initialize(params string[] defaultVariants)
        {
            var stat = Interlocked.CompareExchange(ref mState, STAT_INITIALIZING, STAT_DEFAULT);
            if (stat == STAT_DEFAULT)
            {
                var defaultLen = defaultVariants == null ? 0 : defaultVariants.Length;
                var file = mCfgPath;
                if (string.IsNullOrEmpty(file))
                    file = NativeFileSystem.Combine(ParallelUnity.streamingAssetsPath, "AssetVariants.json");
                try
                {
                    var content = NativeFileSystem.ReadAllText(file);
                    var dt = string.IsNullOrEmpty(content) ? null : JsonMapper.ToObject(content);
                    if(dt == null)
                    {
                        dt = new JsonData();
                        var arr = new JsonData(JsonType.Array);
                        arr.Add("cn");
                        arr.Add("en");
                        dt["Lang"] = arr;
                    }
                    var groups = new List<Group>();
                    foreach (var key in dt.Keys)
                    {
                        var grp = new Group(key, dt[key]);
                        groups.Add(grp);
                    }
                    mVariantGroups = groups.ToArray();
                    for (int i = 0; i < defaultLen; i++)
                    {
                        for (int k = 0; k < mVariantGroups.Length; k++)
                        {
                            mVariantGroups[k].SetVariantActive(defaultVariants[i]);
                        }
                    }
                    Interlocked.Exchange(ref mState, STAT_INITIALIZED);
                    Debug.Log("[Asset] The assets's variant was initialized.");
                    return true;
                }
                catch (System.Exception e)
                {
                    Interlocked.Exchange(ref mState, STAT_ERROR);
                    //Debug.LogException(e);
                    Debug.LogError($"The file 'AssetVariants.json' is not setup correct.\n{e}");
                    return false;
                }
            }
            else
            {
                if (stat == STAT_INITIALIZED && defaultVariants != null && defaultVariants.Length > 0)
                    ActiviteVariants(defaultVariants);
                return stat == STAT_INITIALIZED || stat == STAT_ACTIVITING;
            }
        }

        public IAssetVariantGroup GetVariantGroup(EVariantGroupQuery queryType, string queryValue)
        {
            if (string.IsNullOrEmpty(queryValue))
            {
                //Debug.LogError("The query text is empty (invoke: GetVariantGroup).");
                return null;
            }
            var stat = Interlocked.CompareExchange(ref mState, STAT_INITIALIZED, STAT_INITIALIZED);
            if (stat == STAT_INITIALIZED)
            {
                var qtitle = (queryType & EVariantGroupQuery.Group) != 0;
                var qvariant = (queryType & EVariantGroupQuery.Variant) != 0;
                var qpath = (queryType & EVariantGroupQuery.AssetPath) != 0;
                for (int i = 0; i < mVariantGroups.Length; i++)
                {
                    var grp = mVariantGroups[i];
                    if (qtitle && grp.Title.EqualsIgnoreCase(queryValue))
                        return grp;
                    if (qvariant && grp.GetVariantIndex(queryValue) != -1)
                        return grp;
                    if (qpath && grp.IsVariantAsset(queryValue))
                        return grp;
                }
                return null;
            }
            else
            {
                Debug.LogError("The AssetBundleKeyword is not initialized (invoke: GetVariantGroup).");
                return null;
            }
        }

        public bool GetVariantGroup(string assetPath, out IAssetVariantGroup group, out string variant)
        {
            group = null;
            variant = null;
            if(string.IsNullOrEmpty(assetPath))
            {
                Debug.LogError("The asset path is empty (invoke: GetVariantGroup).");
                return false;
            }
            var stat = Interlocked.CompareExchange(ref mState, STAT_INITIALIZED, STAT_INITIALIZED);
            if (stat == STAT_INITIALIZED)
            {
                for (int i = 0; i < mVariantGroups.Length; i++)
                {
                    var grp = mVariantGroups[i];
                    var match = grp.GetVariantPathPattern().Match(assetPath);
                    if(match != null && match.Success)
                    {
                        group = grp;
                        variant = match.Value;
                        variant = variant.Substring(1, variant.Length - 2);
                        return true;
                    }
                }
                return false;
            }
            else
            {
                Debug.LogError("The AssetBundleKeyword is not initialized (invoke: GetVariantGroup).");
                return false;
            }
        }

        public void ActiviteVariant(string variant)
        {
            if (string.IsNullOrEmpty(variant))
                return;
            var stat = Interlocked.CompareExchange(ref mState, STAT_ACTIVITING, STAT_INITIALIZED);
            if (stat == STAT_INITIALIZED)
            {
                //if (mChangCmd.IsAlive)
                //{
                //    throw new System.InvalidOperationException("Is changing asset's keywords, do it later (invoke: ActiviteKeywords);");
                //}
                mDirtyGroups.Clear();
                for (int i = 0; i < mVariantGroups.Length; i++)
                {
                    var grp = mVariantGroups[i];
                    if (grp.SetVariantActive(variant))
                    {
                        mDirtyGroups.Add(grp);
                    }
                }
                if (mDirtyGroups.Count > 0)
                {
                    mChangCmd = mActivotor.Schedule(true);// ParallelUtils.Schedule(DoActiviteKeywords, true);
                    //mChangCmd.IsIndependent = true;
                    mChangCmd.SampleExecuteTime();
                }
                else
                {
                    Interlocked.CompareExchange(ref mState, STAT_INITIALIZED, STAT_ACTIVITING);
                }
            }
            else
            {
                Debug.LogError("The AssetBundleKeyword is not initialized (invoke: ActiviteKeywords).");
            }
        }

        public void ActiviteVariants(string[] variants)
        {
            var len = variants == null ? 0 : variants.Length;
            if (len == 0)
                return;
            var stat = Interlocked.CompareExchange(ref mState, STAT_ACTIVITING, STAT_INITIALIZED);
            if (stat == STAT_INITIALIZED)
            {
                //if (mChangCmd.IsAlive)
                //{
                //    throw new System.InvalidOperationException("Is changing asset's keywords, do it later (invoke: ActiviteKeywords);");
                //}
                mDirtyGroups.Clear();
                for (int i = 0; i < mVariantGroups.Length; i++)
                {
                    var grp = mVariantGroups[i];
                    for (int k = len - 1; k >= 0; k--)
                    {
                        if (grp.SetVariantActive(variants[k]))
                        {
                            mDirtyGroups.Add(grp);
                            break;
                        }
                    }
                }
                if (mDirtyGroups.Count > 0)
                {
                    mChangCmd = mActivotor.Schedule(true);// ParallelUtils.Schedule(DoActiviteKeywords, true);
                                                          //mChangCmd.IsIndependent = true;
                    mChangCmd.SampleExecuteTime();
                }
                else
                {
                    Interlocked.CompareExchange(ref mState, STAT_INITIALIZED, STAT_ACTIVITING);
                }
            }
            else
            {
                Debug.LogError("The AssetBundleKeyword is not initialized (invoke: ActiviteKeywords).");
            }
        }

        public void JoinActivtionCmd(ParallelDispatcher.Handler job)
        {
            var handle = mDuringChangeCmd;
            if (!handle.IsNull)
            {
                handle.Combine(job);
            }
        }

        class DoActiviteCmd : IYieldCommand, IBreakCommandSignal, IIndependentCommandSignal
        {
            string mTitle;
            AssetBundleVariant mVariant;
            internal DoActiviteCmd(AssetBundleVariant variant)
            {
                mVariant = variant;
                mTitle = "Variant Activitor";
            }

            IEnumerator IYieldCommand.Execute()
            {
                mVariant.mDuringChangeCmd.AbortAll();
                var dirtyGroups = new SliceList<IAssetVariantGroup>(mVariant.mDirtyGroups);
                var buf = ParallelUtils.GetBuilder("Activite variant: ");
                for (int i = 0; i < dirtyGroups.Count; i++)
                {
                    if (i > 0)
                        buf.Append(",");
                    buf.Append(dirtyGroups[i].ActiveVariant);
                }
                mTitle = ParallelUtils.ReturnRelease(buf);
                mVariant.mDuringChangeCmd = ParallelUtils.NewCombination("activite asset's keyword");
                mVariant.OnWillChangeVariants?.Invoke(dirtyGroups);

                var util = AssetsUtil.UtilInstance;
                if (util != null)
                    yield return util.ClearVariant(dirtyGroups);
                mVariant.OnReloadVariants?.Invoke(dirtyGroups);

                yield return mVariant.mDuringChangeCmd;
                mVariant.mDuringChangeCmd = default;

                mVariant.OnLoadedVariants?.Invoke(dirtyGroups);
                mVariant.mDirtyGroups.Clear();
                mVariant.mChangCmd = default;
                Interlocked.CompareExchange(ref mVariant.mState, mVariant.STAT_INITIALIZED, mVariant.STAT_ACTIVITING);
            }

            void IBreakCommandSignal.OnBreak()
            {
                Interlocked.CompareExchange(ref mVariant.mState, mVariant.STAT_INITIALIZED, mVariant.STAT_ACTIVITING);
            }

            bool IIndependentCommandSignal.IsBreakable(bool isInterruptByDependent)
            {
                return false;
            }
            public override string ToString()
            {
                return mTitle;
            }

        }
    }
}
